﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using Com.FastSearch.SharePoint.BL.SearchManager;
using Com.FastSearch.SharePoint.Interfaces;
using Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI;

namespace Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI.Client
{
    /// <summary>
    /// Class for storing search settings and results in web applications.
    /// New class instances will automatically connect with settings and results
    /// for the server round-trip. (See Asp .Net life cycle).
    /// </summary>
    public class SearchRequest : IESPSearchSettings
    {
        private static string CUSTOM_PARAMETER_PREFIX = "cust_param";
        /// <summary>
        /// Search Group id this Search request is used for.
        /// </summary>
        private readonly int _GroupId;
        /// <summary>
        /// Default Query Property name in MOSS
        /// </summary>
        public const string DEFAULT_QUERY_PROPERTY_NAME = "k";
        /// <summary>
        /// Default Sorting Property name in MOSS
        /// </summary>
        public const string CUSTOM_RESULT_SORTING = "v1";

        public const string WEIGHTED_SEARCH_PROPERTY_NAME = "XRANK" ;

        /// <summary>
        /// SearchRequest is keeping search properties in the request scope so all web parts can add/set
        /// search properties depending on their configruation.<br></br>
        /// This information is later used to perform the Search.<br></br>
        /// </summary>
        /// <param name="group"><see cref="ESPSearchGroupEnum"/> specifying the search group this request is used for</param>
        /// <param name="Request"><see cref="HttpRequest"/> used to read the current search request and initialize search parameters for the subsequent search requests.</param>
        public SearchRequest(ESPSearchGroupEnum group, HttpRequest Request)
        {
            _GroupId = (int)group; // use enum int value
            _QueryString = Request.QueryString;
            userQuery = GetSafeQueryString(DEFAULT_QUERY_PROPERTY_NAME);
            CustomResultSorting = GetSafeQueryString(CUSTOM_RESULT_SORTING + PropertyPostfix());
            WeightedSearchQuery = GetSafeQueryString(WEIGHTED_SEARCH_PROPERTY_NAME);
        }

        /// <summary>
        /// Return the parameter prefix, which depends on the search group we are in.
        /// If we are in the search zone 0 no prefix is used. -> an empty string is returned.
        /// Otherwise _(search zone) is returned
        /// Example: For zone 2 this will be _2
        /// </summary>
        /// <returns>url parameter prefix for the zone. See above for details</returns>
        public static string GetPropertyPostfixForGroup(ESPSearchGroupEnum group)
        {
            if (group == ESPSearchGroupEnum.Group0)
            {
                return "";
            }
            return "_" + ((int) group);
        }

        /// <summary>
        /// Build the uri property name prefix for the current search group.
        /// Group0 is not using any prefix. All other properties are using a "_&lt;NUM&gt;" prefix
        /// <example>
        /// If this SearchRequest is in Group 1 then the request parameters are appended with _1.<br>
        /// </br>
        /// <b>Example query parameter:</b>k_1=myqyery
        /// </example>
        /// </summary>
        /// <returns></returns>
        private string PropertyPostfix()
        {
            if (_GroupId != (int)ESPSearchGroupEnum.Group0)
            {
                return "_" + _GroupId;
            }
            return "";
        }

        /// <summary>
        /// Keep the QueryString during the life of this object to be able to build subsequent search links
        /// for navigators and so on.
        /// </summary>
        private readonly NameValueCollection _QueryString;

        #region IESPSearchParams Members

        private string userQuery;
        private string weightedSearchQuery;

        public string WeightedSearchQuery
        {
            get
            {
                return weightedSearchQuery;
            }
            set
            {
                weightedSearchQuery = value;
            }
        }

        /// <inheritDoc/>
        public string UserQuery
        {
            get 
            {
                if (userQuery == null || !string.IsNullOrEmpty(QueryURLPropertyName))
                {
                    userQuery = GetSafeQueryString(QueryURLPropertyName);
                }
                return userQuery;
            }
        }

        /// <inheritDoc/>
        public string[] Nav
        {
            get
            {
                return GetSafeMultipleQueryString("nav" + PropertyPostfix());
            }
        }

        /// <inheritDoc/>
        public string NavigationParameter
        {
            get
            {
                return BuildNav("nav", false);
            }
        }

        /// <inheritDoc/>
        public string[] ScopeNav
        {
            get
            {
                return GetSafeMultipleQueryString("scopenav" + PropertyPostfix());
            }
        }

        /// <inheritDoc/>
        public string ScopeNavigationParameter
        {
            get
            {
                return BuildNav("scopenav", true);
            }
        }

        /// <inheritDoc/>
        public char NavSplitCharacter
        {
            get { return '\u01C1'; }
        }

        private int start1 = -1;

        /// <inheritDoc/>
        public int Start1
        {
            get
            {
                if (start1 == -1)
                {
                    int x;
                    int.TryParse(GetSafeQueryString("start1" + PropertyPostfix()), out x);
                    start1 = x;
                }
                return start1;
            }
        }

        #endregion

        #region IESPSearchSettings Members

        /// <inheritDoc/>
        public string CustomResultSorting
        {
            get
            {
                return (string)GetSafeHttpContext(CUSTOM_RESULT_SORTING, null);
            }
            set
            {
                SetSafeHttpContext(CUSTOM_RESULT_SORTING, value);
            }
        }

        /// <inheritDoc/>
        public int NumberOfResultsPerPage
        {
            get
            {
                return (int) GetSafeHttpContext("NumberOfResultsPerPage", 0);
            }
            set
            {
                SetSafeHttpContext("NumberOfResultsPerPage", value);
            }
        }

        /// <inheritDoc/>
        public string ESPSearchView
        {
            get
            {
                return GetSafeHttpContextString("ESPSearchView", null);
            }
            set
            {
                SetSafeHttpContext("ESPSearchView", value);
            }
        }

        /// <inheritDoc/>
        public string LemmatizationEnabled
        {
            get
            {
                return GetSafeHttpContextString("ESPLemmatization", "false");
            }
            set
            {
                SetSafeHttpContext("ESPLemmatization", value);
            }
        }

        /// <inheritDoc/>
        public string SynonymsEnabled
        {
            get
            {
                return GetSafeHttpContextString("ESPSynonyms", "false");
            }
            set
            {
                SetSafeHttpContext("ESPSynonyms", value);
            }
        }

        /// <inheritDoc/>
        public string Spellcheck
        {
            get
            {
                return GetSafeHttpContextString("ESPSpellcheck", "false");
            }
            set
            {
                SetSafeHttpContext("ESPSpellcheck", value);
            }
        }

        /// <inheritDoc/>
        public string ResultSorting
        {
            get
            {
                return GetSafeHttpContextString("ESPResultSorting", null);
            }
            set
            {
                SetSafeHttpContext("ESPResultSorting", value);
            }
        }

        /// <inheritDoc/>
        public string QueryTemplate
        {
            get
            {
                return GetSafeHttpContextString("ESPQueryTemplate", null);
            }
            set
            {
                SetSafeHttpContext("ESPQueryTemplate", value);
            }
        }

        /// <inheritDoc/>
        public string StaticCustomQueryProperties
        {
            get
            {
                return GetSafeHttpContextString("ESPCustomProperties", null);
            }
            set
            {
                SetSafeHttpContext("ESPCustomProperties", value);
            }
        }

        /// <inheritDoc/>
        public string QueryURLPropertyName
        {
            get
            {
                string val = GetSafeHttpContextString("ESPQueryURLPropertyName", null);
                if ( string.IsNullOrEmpty(val))
                {
                    return DEFAULT_QUERY_PROPERTY_NAME;
                }
                return val;
            }
            set
            {
                SetSafeHttpContext("ESPQueryURLPropertyName", value);
            }
        }

        /// <inheritDoc/>
        public string ESPConnection
        {
            get
            {
                string val = GetSafeHttpContextString("ESPConnection", null);
                if ( string.IsNullOrEmpty(val))
                {
                    return null;
                }
                return val;
            }
            set
            {
                SetSafeHttpContext("ESPConnection", value);
            }
        }

        /// <inheritDoc/>
        public string Navigation
        {
            get
            {
                IList<IESPNavigatorDefinition> navigators = (IList<IESPNavigatorDefinition>) GetSafeHttpContext("ESPNavigators", null);
                if (navigators == null)
                {
                    return null;
                }
                StringBuilder sb = new StringBuilder();
                foreach (IESPNavigatorDefinition navigator in navigators)
                {
                    if (sb.Length > 0)
                    {
                        sb.Append(',');
                    }
                    sb.Append(navigator.Name);
                    if (navigator.IsScopeNavigator)
                    {
                        sb.Append("(context=");
                        sb.Append(navigator.Scope);
                        sb.Append("!,display=");
                        sb.Append(navigator.Scope);
                        sb.Append(')');
                    }
                }
                return sb.ToString();
            }
        }

        /// <inheritDoc/>
        public void AddNavigator(IESPNavigatorDefinition navigator)
        {
            if (navigator != null)
            {
                IList<IESPNavigatorDefinition> navigators = (IList<IESPNavigatorDefinition>)GetSafeHttpContext("ESPNavigators", null);
                if (navigators == null)
                {
                    SetSafeHttpContext("ESPNavigators", navigators = new List<IESPNavigatorDefinition>());
                }
                navigators.Add(navigator);
            }
        }

        #endregion

        #region Helper Functions

        /// <summary>
        /// Get a context parameter parameter. <br></br>
        /// If the context parameter parameter is not available the defaultValue is returned.
        /// </summary>
        /// <param name="name">context parameter parameter name</param>
        /// <param name="defaultValue">default value to be returned if the parameter is not available</param>
        /// <returns>parameter value if available, the default value parameter otherwise</returns>
        private string GetSafeHttpContextString(string name, string defaultValue)
        {
            return (string) GetSafeHttpContext(name, defaultValue);
        }

        /// <summary>
        /// Get a context parameter in a secure fassion.<br></br>
        /// If the context parameter is not available the defaultValue is returned.
        /// </summary>
        /// <param name="name">context parameter parameter name</param>
        /// <param name="defaultValue">default value to be returned if the parameter is not available</param>
        /// <returns>parameter value if available, the default value parameter otherwise</returns>
        private object GetSafeHttpContext(string name, object defaultValue)
        {
            if (HttpContext.Current.Items.Contains(name + PropertyPostfix()))  //PropertyPostfix
            {
                return HttpContext.Current.Items[name + PropertyPostfix()];
            }
            return defaultValue;
        }

        /// <summary>
        /// Set a context parameter
        /// </summary>
        /// <param name="name">context parameter name</param>
        /// <param name="value">context parameter value</param>
        private void SetSafeHttpContext(string name, object value)
        {
            HttpContext.Current.Items[name + PropertyPostfix()] = value;
        }

        /// <summary>
        /// Return a uri parameter.<br></br>
        /// If the uri parameter is not existing, null is returned.
        /// </summary>
        /// <param name="property">uri parameter</param>
        /// <returns>uri parameter value if available, null otherwise</returns>
        private string GetSafeQueryString(string property)
        {
            return GetSafeQueryString(property, null);
        }

        /// <summary>
        /// Return a uri parameter.<br></br>
        /// If the uri parameter is not existing, the passed in defaultValue method parameter is returned.
        /// </summary>
        /// <param name="property">uri parameter</param>
        /// <param name="defaultValue">default value to be returned if the query parameter is not available</param>
        /// <returns>uri parameter value if available, the default value otherwise</returns>
        private string GetSafeQueryString(string property, string defaultValue)
        {
            try
            {
                return _QueryString[property];
            }
            catch (NullReferenceException)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Return all values for a query parameter for properties which can exist multiple times
        /// </summary>
        /// <param name="property">property name</param>
        /// <returns>string array of all values. empty string array if none is found</returns>
        private string[] GetSafeMultipleQueryString(string property)
        {
            return _QueryString.GetValues(property) ?? new string[0];
        }

        /// <summary>
        /// Build a navigation search parameter from the specified request parameter.
        /// </summary>
        /// <param name="param">The name of the param, excluding the group id postfix</param>
        /// <param name="scoped">Whether or not the navigation is scoped</param>
        /// <returns>A navigation parameter</returns>
        private string BuildNav(string param, bool scoped)
        {
            char[] split = new char[] { NavSplitCharacter };
            StringBuilder sb = new StringBuilder();
            foreach (string modification in GetSafeMultipleQueryString(param + PropertyPostfix()))
            {
                if (sb.Length > 0)
                {
                    sb.Append(' ');
                }
                string[] parts = modification.Split(split);
                if (parts.Length == 4)
                {
                    if (scoped)
                    {
                        sb.Append(parts[0]);
                        sb.Append(':');
                        if (!string.IsNullOrEmpty(parts[3]))
                        {
                            sb.Append(parts[3].Replace("@", ":@"));
                            sb.Append(':');
                        }
                        sb.Append(parts[1]);
                    }
                    else
                    {
                        sb.Append('+');
                        sb.Append(parts[0]);
                        sb.Append(':');
                        sb.Append(parts[1]);
                    }
                }
            }
            return sb.ToString();
        }

        #endregion

        #region IESPSearchSettings Members


        /// <inheritdoc/>
        /// <exception cref="SearchRequestParameterException"> if parameterName is null or empty or parameterValue is null</exception>
        public void SetCustomParameterByName(string parameterName, string parameterValue)
        {

            if (string.IsNullOrEmpty(parameterName))
            {
                throw new SearchRequestParameterException("parameterName cannot be null or an empty string!");
            }
            if (parameterValue == null)
            {
                throw new SearchRequestParameterException("parameterValue cannot be null!");
            }

            SetSafeHttpContext(CUSTOM_PARAMETER_PREFIX + parameterName, parameterValue);            
        }

        /// <inheritdoc/>
        public string GetCustomParameterByName(string parameterName)
        {
            return (string)GetSafeHttpContext(CUSTOM_PARAMETER_PREFIX + parameterName, null);
        }

        /// <inheritdoc/>
        public List<KeyValuePair<string,string>> GetAllCustomParameter()
        {
            List<KeyValuePair<string,string>> list = new List<KeyValuePair<string,string>>();

            System.Collections.ICollection keys = HttpContext.Current.Items.Keys;
            foreach (Object key in keys)
            {
                if (key is string)
                {
                    string keystr = (string)key;
                    if (keystr != null && keystr.StartsWith(CUSTOM_PARAMETER_PREFIX))
                    {
                        if (keystr.Length > CUSTOM_PARAMETER_PREFIX.Length)
                        {
                            string origKey = keystr.Substring(CUSTOM_PARAMETER_PREFIX.Length);
                            string groupId = PropertyPostfix();
                            if (!string.IsNullOrEmpty(groupId))
                            {
                                origKey = origKey.Substring(0, origKey.Length - groupId.Length);
                            }

                            list.Add(new KeyValuePair<string, string>(origKey,
                                GetCustomParameterByName(origKey)));
                        }
                    }
                }
                
            }
            return list;

        }

        #endregion
    }

    /// <summary>
    /// Exception which indicates that a custom parameter setting is wrong
    /// </summary>
    public class SearchRequestParameterException: Exception {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message">Message of the Exception</param>
        public SearchRequestParameterException(string message)
            : base(message)
        {

        }
    }
}
